package pgex

import (
	"context"
	"fmt"
	"time"

	"github.com/FeifeiyuM/sqly"
	_ "github.com/lib/pq"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
)

var (
	// ErrEmpty 空错误
	ErrEmpty = sqly.ErrEmpty
)

type (
	Int64Array = sqly.Int64Array
	Int32Array = sqly.Int32Array
)

// SqlEx sql tracing 扩展
type SqlEx struct {
	capsule *sqly.Capsule
}

// NewSqlEx new a sql connection instance
func NewSqlEx(host, port, user, password, dbname string, maxIdelConns, maxOpenConns int, connMaxLifeTime time.Duration) (*SqlEx, error) {
	dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable connect_timeout=2 TimeZone=Asia/Shanghai",
		host, port, user, password, dbname)

	opt := &sqly.Option{
		Dsn:             dsn,
		DriverName:      "postgres",
		MaxIdleConns:    maxIdelConns,
		MaxOpenConns:    maxOpenConns,
		ConnMaxLifeTime: connMaxLifeTime,
	}

	db, err := sqly.New(opt)
	if err != nil {
		return nil, err
	}
	if err := db.Ping(); err != nil {
		return nil, err
	}
	return &SqlEx{capsule: sqly.NewCapsule(db)}, nil
}

// Close close connection
func (s *SqlEx) Close() error {
	return s.capsule.Close()
}

func getSpan(span opentracing.Span, query string, args interface{}) opentracing.Span {
	newSpan := span.Tracer().StartSpan("mysql", opentracing.ChildOf(span.Context()))
	ext.SpanKindRPCClient.Set(newSpan)
	ext.DBType.Set(newSpan, "mysql")
	ext.DBStatement.Set(span, query)
	span.LogKV("args", args)
	return newSpan
}

// Exec 执行 sql
func (s *SqlEx) Exec(ctx context.Context, query string, args ...interface{}) (*sqly.Affected, error) {
	// tracing settings
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.Exec(ctx, query, args...)
}

// Query query rows
func (s *SqlEx) Query(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
	// tracing settings
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.Query(ctx, dest, query, args...)
}

// Get query one
func (s *SqlEx) Get(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
	// tracing settings
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.Get(ctx, dest, query, args...)
}

// Insert insert one row
func (s *SqlEx) Insert(ctx context.Context, query string, args ...interface{}) (*sqly.Affected, error) {
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.Insert(ctx, query, args...)
}

// InsertMany insert many rows
func (s *SqlEx) InsertMany(ctx context.Context, query string, args [][]interface{}) (*sqly.Affected, error) {
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.InsertMany(ctx, query, args)
}

// Update update
func (s *SqlEx) Update(ctx context.Context, query string, args ...interface{}) (*sqly.Affected, error) {
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.Update(ctx, query, args...)
}

// UpdateMany update many
func (s *SqlEx) UpdateMany(ctx context.Context, query string, args [][]interface{}) (*sqly.Affected, error) {
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.UpdateMany(ctx, query, args)
}

// Delete delete
func (s *SqlEx) Delete(ctx context.Context, query string, args ...interface{}) (*sqly.Affected, error) {
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.Delete(ctx, query, args...)
}

// postgresql insert return with id
func (s *SqlEx) PgExec(ctx context.Context, idField, query string, args ...interface{}) (*sqly.Affected, error) {
	if span := opentracing.SpanFromContext(ctx); span != nil {
		span := getSpan(span, query, args)
		defer span.Finish()
		ctx = opentracing.ContextWithSpan(ctx, span)
	}
	return s.capsule.PgExec(ctx, idField, query, args...)
}

// IsTrans is trans
func (s *SqlEx) IsTrans(ctx context.Context) (bool, error) {
	return s.capsule.IsTrans(ctx)
}

// StartCapsule 开启胶囊
func (s *SqlEx) StartCapsule(ctx context.Context, isTrans bool, capFunc func(ctx context.Context) (interface{}, error)) (interface{}, error) {
	return s.capsule.StartCapsule(ctx, isTrans, capFunc)
}
